<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>无缝轮播图-经典</title>
</head>

<body>


  原生javascript手写一个丝滑的轮播图 https://juejin.cn/post/7169471068539715598

  <div class="carousel-container">
    <div class="carousel-box">
      <div class="carousel-content">
        <div class="carousel-item active">
          <img src="https://www.eveningwater.com/img/segmentfault/1.png" alt="" class="carousel-item-img" />
        </div>
        <div class="carousel-item">
          <img src="https://www.eveningwater.com/img/segmentfault/2.png" alt="" class="carousel-item-img" />
        </div>
        <div class="carousel-item">
          <img src="https://www.eveningwater.com/img/segmentfault/3.png" alt="" class="carousel-item-img" />
        </div>
        <div class="carousel-item">
          <img src="https://www.eveningwater.com/img/segmentfault/4.png" alt="" class="carousel-item-img" />
        </div>
        <div class="carousel-item">
          <img src="https://www.eveningwater.com/img/segmentfault/5.png" alt="" class="carousel-item-img" />
        </div>
        <div class="carousel-item">
          <img src="https://www.eveningwater.com/img/segmentfault/6.png" alt="" class="carousel-item-img" />
        </div>
        <div class="carousel-item">
          <img src="https://www.eveningwater.com/img/segmentfault/7.png" alt="" class="carousel-item-img" />
        </div>
      </div>
      <div class="carousel-sign">
        <div class="carousel-sign-item active">0</div>
        <div class="carousel-sign-item">1</div>
        <div class="carousel-sign-item">2</div>
        <div class="carousel-sign-item">3</div>
        <div class="carousel-sign-item">4</div>
        <div class="carousel-sign-item">5</div>
        <div class="carousel-sign-item">6</div>
      </div>
      <div class="carousel-ctrl carousel-left-ctrl">&lt;</div>
      <div class="carousel-ctrl carousel-right-ctrl">&gt;</div>
    </div>
  </div>

  <style>
    .carousel-box,
    .carousel-content,
    .carousel-item,
    .carousel-item-img {
      width: 100%;
      height: 100%;
    }

    .carousel-box,
    .carousel-item {
      position: relative;
    }

    .carousel-box {
      overflow: hidden;
    }

    .carousel-item {
      display: none;
    }

    .carousel-item.active {
      display: block;
      left: 0;
    }

    .carousel-item-img {
      object-fit: cover;
    }

    .carousel-item.active.left {
      left: -100%;
    }

    .carousel-item.active.right {
      left: 100%;
    }

    .carousel-item.next,
    .carousel-item.prev {
      display: block;
      position: absolute;
      top: 0;
    }

    .carousel-item.next {
      left: 100%;
    }

    .carousel-item.prev {
      left: -100%;
    }

    .carousel-item.next.left,
    .carousel-item.prev.right {
      left: 0%;
    }

    .carousel-sign {
      position: absolute;
      bottom: 10px;
      left: 50%;
      transform: translateX(-50%);
      padding: 5px 3px;
      border-radius: 6px;
      user-select: none;
      background: linear-gradient(135deg, #73a0e4 10%, #1467e4 90%);
    }

    .carousel-sign-item {
      width: 22px;
      height: 20px;
      font-size: 14px;
      font-weight: 500;
      line-height: 20px;
      text-align: center;
      float: left;
      color: #f2f3f4;
      margin: auto 4px;
      cursor: pointer;
      border-radius: 4px;
    }

    .carousel-sign-item:hover {
      color: #fff;
    }

    .carousel-sign-item.active {
      color: #535455;
      background-color: #ebebeb;
    }

    .carousel-ctrl {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      font-size: 30px;
      font-weight: 300;
      user-select: none;
      background: linear-gradient(135deg, #73a0e4 10%, #1467e4 90%);
      color: #fff;
      border-radius: 5px;
      cursor: pointer;
      transition: all 0.1s cubic-bezier(0.075, 0.82, 0.165, 1);
      text-align: center;
      padding: 1rem;
    }

    .carousel-ctrl.carousel-left-ctrl {
      left: -50px;
    }

    .carousel-ctrl.carousel-right-ctrl {
      right: -50px;
    }

    .carousel-box:hover .carousel-ctrl.carousel-left-ctrl {
      left: 10px;
    }

    .carousel-box:hover .carousel-ctrl.carousel-right-ctrl {
      right: 10px;
    }

    .carousel-ctrl:hover {
      background-color: rgba(0, 0, 0, 0.8);
    }

    .carousel-container {
      width: 500px;
      height: 400px;
      margin: auto;
    }
  </style>

  <script>
    const $ = (v, el = document) => el.querySelector(v);
    const $$ = (v, el = document) => el.querySelectorAll(v);
    class Carousel {
      constructor(options) {
        this.container = $(options.el);
        this.carouselItems = this.container.querySelectorAll(".carousel-item");
        this.carouselSigns = $$(
          ".carousel-sign .carousel-sign-item",
          this.container
        );
        this.carouselCtrlL = $$(".carousel-ctrl", this.container)[0];
        this.carouselCtrlR = $$(".carousel-ctrl", this.container)[1];

        // 当前图片索引
        this.curIndex = 0;
        // 轮播盒内图片数量
        this.numItems = this.carouselItems.length;

        // 是否可以滑动
        this.status = true;

        // 轮播速度
        this.speed = options.speed || 600;
        // 等待延时
        this.delay = options.delay || 3000;

        // 轮播方向
        this.direction = options.direction || "left";

        // 是否监听键盘事件
        this.monitorKeyEvent = options.monitorKeyEvent || false;
        // 是否监听屏幕滑动事件
        this.monitorTouchEvent = options.monitorTouchEvent || false;

        this.handleEvents();
        this.setTransition();
      }

      // 开始轮播
      start() {
        const event = {
          srcElement:
            this.direction == "left" ? this.carouselCtrlR : this.carouselCtrlL,
        };
        const clickCtrl = this.clickCtrl.bind(this);
        // 每隔一段时间模拟点击控件
        this.interval = setInterval(clickCtrl, this.delay, event);
      }

      // 暂停轮播
      pause() {
        clearInterval(this.interval);
      }

      /**
       * 设置轮播图片的过渡属性
       * 在文件头内增加一个样式标签
       * 标签内包含轮播图的过渡属性
       */
      setTransition() {
        const styleElement = document.createElement("style");
        document.head.appendChild(styleElement);
        const styleRule = `.carousel-item {transition: left ${this.speed}ms ease-in-out}`;
        styleElement.sheet.insertRule(styleRule, 0);
      }

      // 处理点击控件事件
      clickCtrl(event) {
        if (!this.status) return;
        this.status = false;
        let fromIndex = this.curIndex,
          toIndex,
          direction;
        if (event.srcElement == this.carouselCtrlR) {
          toIndex = (this.curIndex + 1) % this.numItems;
          direction = "left";
        } else {
          toIndex = (this.curIndex + this.numItems - 1) % this.numItems;
          direction = "right";
        }
        this.slide(fromIndex, toIndex, direction);
        this.curIndex = toIndex;
      }

      // 处理点击标志事件
      clickSign(event) {
        if (!this.status) return;
        this.status = false;
        const fromIndex = this.curIndex;
        const toIndex = parseInt(event.srcElement.getAttribute("slide-to"));
        const direction = fromIndex < toIndex ? "left" : "right";
        this.slide(fromIndex, toIndex, direction);
        this.curIndex = toIndex;
      }

      // 处理滑动屏幕事件
      touchScreen(event) {
        if (event.type == "touchstart") {
          this.startX = event.touches[0].pageX;
          this.startY = event.touches[0].pageY;
        } else {
          // touchend
          this.endX = event.changedTouches[0].pageX;
          this.endY = event.changedTouches[0].pageY;

          // 计算滑动方向的角度
          const dx = this.endX - this.startX;
          const dy = this.startY - this.endY;
          const angle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI);

          // 滑动距离太短
          if (Math.abs(dx) < 10 || Math.abs(dy) < 10) return;

          if (angle >= 0 && angle <= 45) {
            // 向右侧滑动屏幕，模拟点击左控件
            this.carouselCtrlL.click();
          } else if (angle >= 135 && angle <= 180) {
            // 向左侧滑动屏幕，模拟点击右控件
            this.carouselCtrlR.click();
          }
        }
      }

      // 处理键盘按下事件
      keyDown(event) {
        if (event && event.keyCode == 37) {
          this.carouselCtrlL.click();
        } else if (event && event.keyCode == 39) {
          this.carouselCtrlR.click();
        }
      }

      // 处理各类事件
      handleEvents() {
        // 鼠标移动到轮播盒上时继续轮播
        this.container.addEventListener("mouseleave", this.start.bind(this));
        // 鼠标从轮播盒上移开时暂停轮播
        this.container.addEventListener("mouseover", this.pause.bind(this));

        // 点击左侧控件向右滑动图片
        this.carouselCtrlL.addEventListener("click", this.clickCtrl.bind(this));
        // 点击右侧控件向左滑动图片
        this.carouselCtrlR.addEventListener("click", this.clickCtrl.bind(this));

        // 点击轮播标志后滑动到对应的图片
        for (let i = 0; i < this.carouselSigns.length; i++) {
          this.carouselSigns[i].setAttribute("slide-to", i);
          this.carouselSigns[i].addEventListener(
            "click",
            this.clickSign.bind(this)
          );
        }

        // 监听键盘事件
        if (this.monitorKeyEvent) {
          document.addEventListener("keydown", this.keyDown.bind(this));
        }

        // 监听屏幕滑动事件
        if (this.monitorTouchEvent) {
          this.container.addEventListener(
            "touchstart",
            this.touchScreen.bind(this)
          );
          this.container.addEventListener(
            "touchend",
            this.touchScreen.bind(this)
          );
        }
      }

      /**
       * 滑动图片
       * @param {number} fromIndex
       * @param {number} toIndex
       * @param {string} direction
       */
      slide(fromIndex, toIndex, direction) {
        let fromClass, toClass;
        if (direction == "left") {
          this.carouselItems[toIndex].className = "carousel-item next";
          fromClass = "carousel-item active left";
          toClass = "carousel-item next left";
        } else {
          this.carouselItems[toIndex].className = "carousel-item prev";
          fromClass = "carousel-item active right";
          toClass = "carousel-item prev right";
        }
        this.carouselSigns[fromIndex].className = "carousel-sign-item";
        this.carouselSigns[toIndex].className = "carousel-sign-item active";

        setTimeout(
          (() => {
            this.carouselItems[fromIndex].className = fromClass;
            this.carouselItems[toIndex].className = toClass;
          }).bind(this),
          50
        );

        setTimeout(
          (() => {
            this.carouselItems[fromIndex].className = "carousel-item";
            this.carouselItems[toIndex].className = "carousel-item active";
            this.status = true; // 设置为可以滑动
          }).bind(this),
          this.speed + 50
        );
      }
    }

    // 轮播选项
    const options = {
      el: ".carousel-box",
      speed: 1000, // 轮播速度(ms)
      delay: 0, // 轮播延迟(ms)
      direction: "left", // 图片滑动方向
      monitorKeyEvent: true, // 是否监听键盘事件
      monitorTouchEvent: true, // 是否监听屏幕滑动事件
    };
    const carouselInstance = new Carousel(options);
    carouselInstance.start();
  </script>
</body>

</html>